Skip to content

Conversation

joshlf
Copy link

@joshlf joshlf commented Jul 11, 2025

Replace a number of lines of unsafe code with safe equivalents, some using zerocopy. In one instance, fix a soundness hole (#720).

Closes #720

@joshlf joshlf force-pushed the remove-unsafe branch 5 times, most recently from c46b513 to 745b847 Compare July 11, 2025 19:00
@ChrisDenton
Copy link
Member

Adding a dependency on zerocopy means that zerocopy becomes a dependency of std.

That might be fine! But it is something that will need to be considered.

Cargo.toml Outdated
@@ -28,6 +28,7 @@ exclude = [
[dependencies]
cfg-if = "1.0"
rustc-demangle = "0.1.24"
zerocopy = { version = "0.8.26", features = ["derive"] }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this won't work. The standard library can't depend on proc-macros without completely breaking cross-compilation.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given #720 (comment), I'll need to go back to the drawing board on this anyway. I'll see how much of this I can salvage without relying on derives.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While the definitions are auto-generated, it is still possible to add an impl block outside of that file. It's all crate-local anyway. I dunno if that helps you.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That wouldn't really help with the safety - the point of the derives is the analysis they perform which validates that certain safety properties hold. While it would technically work to write the impls by hand, it wouldn't improve the safety situation.

@joshlf
Copy link
Author

joshlf commented Jul 11, 2025

Okay, I've removed all uses of derives to address #721 (comment) and #720 (comment).

@joshlf
Copy link
Author

joshlf commented Aug 27, 2025

Friendly ping on this 🙂

@joshlf
Copy link
Author

joshlf commented Aug 27, 2025

Adding a dependency on zerocopy means that zerocopy becomes a dependency of std.

That might be fine! But it is something that will need to be considered.

Oh sorry I should have mentioned this before: zerocopy may already be a dependency of std. It appears in Rust's Cargo.lock, although I haven't traced it yet to figure out where that dep comes from.

@philipc
Copy link
Contributor

philipc commented Aug 28, 2025

I think std dependencies are in the library Cargo.lock. And probably zerocopy would need the rustc-dep-of-std hack too.

@joshlf
Copy link
Author

joshlf commented Aug 28, 2025

I think std dependencies are in the library Cargo.lock. And probably zerocopy would need the rustc-dep-of-std hack too.

Ah gotcha. Well we'd be happy to add that hack if need be - if y'all decide that this PR is good to merge, then we can add it.


let hdr: *const [u32; 3] = hdr;
// SAFETY: `[u32; 3]` and `Elf_Nhdr` have the same layout.
Some(unsafe { &*hdr.cast::<Elf_Nhdr>() })
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this fix anything, or only replace one unsafe usage with another?

If it fixes something, how simple would it be to fix that without the dependency?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this fix anything

Yeah, it fixes a soundness hole in the original – in particular, that alignment isn't checked. This comment in the original is misleading:

    // Note that sice_of::<Elf_Nhdr>() is always 4-byte aligned.
    *bytes = &bytes[size_of::<Elf_Nhdr>()..];

While it's true that size_of::<Elf_Nhdr>() is a multiple of 4, there's no guarantee about the alignment of bytes itself, and so &bytes[size_of::<Elf_Nhdr>()..] is similarly not guaranteed to be aligned. As far as I can tell (from reading this git blame on this file), that's just an oversight that dates back to when this code was first written.

...or only replace one unsafe usage with another?

The unsafe usage in the new version is required so long as Elf_Nhdr doesn't implement some zerocopy traits. Implementing them would require a proc macro derive, which it sounds like isn't an option.

That said, the new unsafe usage is simpler than the old one - it only relies on [u32; 3] and Elf_Nhdr having the same layout and bit validity, while the preceding ref_from_prefix is doing the heavy lifting of making sure that the &[u8] to &[u32; 3] cast doesn't violate bit validity and performs runtime validation of size and alignment.

If it fixes something, how simple would it be to fix that without the dependency?

It wouldn't be hard in principle. The concern is more that it'd be brittle - it would be easy for a future developer who doesn't understand the subtleties we're discussing here to re-introduce a soundness bug. IMO the biggest clue to this is the "sice_of::<Elf_Nhdr>() is always 4-byte aligned" comment, which is patently incorrect and yet made it into the codebase and past code review. I won't call anyone out here by name, but if you git blame this file, you'll see that it was written by a very experienced Rust programmer (and one whom I personally hold in high regard) – if an experienced programmer could make this mistake, then presumably it's an easy and subtle mistake to make.

Copy link
Contributor

@philipc philipc Aug 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO the biggest clue to this is the "sice_of::<Elf_Nhdr>() is always 4-byte aligned" comment, which is patently incorrect and yet made it into the codebase and past code review

I don't think that comment is so badly wrong. It could be clearer, but I take that comment to mean that it preserves the 4-byte alignment required by ELF, which is what is important for that line.

The thing that is wrong is that the function doesn't check the 4-byte alignment to start with, and the comment prior to this function thinks that this requirement is optional.

@@ -15,6 +15,7 @@ bench = false
cfg-if = "1.0"
rustc-demangle = "0.1.21"
libc = { version = "0.2.156", default-features = false }
zerocopy = "0.8.26"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If kept, this could be restricted to target_os = "fuchsia".

Copy link
Member

@bjorn3 bjorn3 Aug 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also can you please check that zerocopy-derive doesn't end up in the lockfile? If it does end up in there, that will cause a tidy lint checking that the standard library doesn't depend on proc-macros to fail.

Edit: It does end up in the lockfile.

Replace a number of lines of unsafe code with safe equivalents, some
using `zerocopy`. In one instance, fix a soundness hole (rust-lang#720).

Closes rust-lang#720

let hdr: *const [u32; 3] = hdr;
// SAFETY: `[u32; 3]` and `Elf_Nhdr` have the same layout and bit validity.
Some(unsafe { &*hdr.cast::<Elf_Nhdr>() })
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The backtrace crate already indirectly depends on object, right? Maybe you could use the object::elf::NoteHeader32/64 type instead and then use object::pod::from_bytes() instead of the transmute?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That seems easy enough, see #725. object is already a direct dependency.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh cool, TIL about object::pod.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Soundess bug: &mut reference exposes uninitialized bytes
4 participants